package LatentTicTacToe.misc;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.HashMap;
import java.util.LinkedList;

import LatentTicTacToe.Calculators.TreeUpdater;
import LatentTicTacToe.Data.AbstractBoard;
import LatentTicTacToe.Data.Move;
import LatentTicTacToe.InformationSets.CFRInformationSet;

/**
 * Class where game tree is stored, with methods for building updating loading
 * and saving game tree
 * 
 * @author Jiri Cermak
 * 
 */
public class GameTreeBuilder {
	
	private TreeUpdater calculator;
	private AbstractBoard initialBoard;

	/**
	 * HashMap containing all information sets of game tree
	 */
	private HashMap<String, CFRInformationSet> gameTree;

	public GameTreeBuilder(TreeUpdater calculator, AbstractBoard initialBoard) {
		this.calculator = calculator;
		this.initialBoard = initialBoard;
	}

	/**
	 * If available returns hashmap with information sets, if not creates one
	 * 
	 * @return hashmap with information sets
	 */
	public HashMap<String, CFRInformationSet> getGameTree() {

		if (gameTree == null) {
			try {
				loadGameTree("1000IterationsTree");
			} catch (FileNotFoundException e) {

				System.out.println("Game tree file not found. Generating new one...");
				
				gameTree = new HashMap<String, CFRInformationSet>(1000000);

				buildCompleteTree(initialBoard, (byte) 1);
				
				calculator.setGameTree(gameTree);
				
				calculator.updateTree(1000);

				saveGameTree("1000IterationsTree");

			} catch (IOException e) {
				e.printStackTrace();
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			}
		}

		return this.gameTree;
	}

	/**
	 * Method which fills hashMap informationSets with all information sets
	 * 
	 * @param board
	 * @param sign
	 */
	private void buildCompleteTree(AbstractBoard board, byte sign) {

		if (board.isTerminal() != 0) {
			gameTree.put(board.id, new CFRInformationSet(board, sign, board.isTerminal()));
		} else {

			LinkedList<Move> moves = new LinkedList<Move>();

			moves.addAll(board.getSuccessors());

			if (gameTree.get(board.id) == null)
				gameTree.put(board.id, new CFRInformationSet(board, sign, (byte) 0));

			for (Move successor : moves) {

				if (board.isValid(successor)) {

					board.makeMove(successor, sign);

					buildCompleteTree(board, (byte) (3 - sign));

					board.reverseMove();

				} else {

					board.makeMove(successor, sign);

					addSingleState(board, sign, successor);

					board.reverseMove();

				}
			}
		}
	}

	/**
	 * Method which creates state with full information and continues with tree
	 * creation
	 * 
	 * @param board
	 * @param move
	 * @param sign
	 */
	@SuppressWarnings("unchecked")
	private void addSingleState(AbstractBoard board, byte sign, Move wrongMove) {

		LinkedList<Move> tempMoves = (LinkedList<Move>) board.getSuccessors().clone();

		tempMoves.remove(wrongMove);

		if (gameTree.get(board.id) == null) {
			gameTree.put(board.id, new CFRInformationSet(board, sign, (byte) 0));
		}
	}

	/**
	 * Saves created game tree to file
	 * 
	 * @param fileName
	 */
	private void saveGameTree(String fileName) {

		File file = new File(fileName);
		FileOutputStream f;

		try {
			f = new FileOutputStream(file);
			ObjectOutputStream s = new ObjectOutputStream(new BufferedOutputStream(f));
			s.writeObject(gameTree);

			s.close();
			f.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Loads previously generated game tree
	 * 
	 * @param fileName
	 * @throws FileNotFoundException
	 * @throws ClassNotFoundException
	 */
	@SuppressWarnings("unchecked")
	private void loadGameTree(String fileName) throws FileNotFoundException, IOException, ClassNotFoundException {

		File file = new File(fileName);
		FileInputStream f;

		f = new FileInputStream(file);

		ObjectInputStream s = new ObjectInputStream(new BufferedInputStream(f));

		gameTree = (HashMap<String, CFRInformationSet>) s.readObject();

		s.close();
		f.close();

	}
}
